package com.sxf.core.db.datasource;


import com.sxf.core.db.common.DynamicDataSourceGlobal;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @description: 动态数据源插件
 * @author: zhangyb@ccccit.com.cn 
 * @date: 2021年1月7日 下午4:50:02
 */
@Intercepts({
	@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
	@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class DynamicPlugin implements Interceptor {

	private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

	private static final Map<String, DynamicDataSourceGlobal> CACHE_MAP = new ConcurrentHashMap<>();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
	    /**
	     * 判断是否有事务，如果有事务的话，即用默认数据源（写数据源）
	     */
	    if(!TransactionSynchronizationManager.isSynchronizationActive()) {
	        DynamicDataSourceHolder.putDataSource(selectDatasource(invocation));
	    }
		return invocation.proceed();
	}
	
	private DynamicDataSourceGlobal selectDatasource(Invocation invocation) {
	    Object[] objects = invocation.getArgs();
	    MappedStatement ms = (MappedStatement) objects[0];

	    /**
	     * 判断是否有包含查询，例如：在数据库中自定义seq。每一次插入或者查询时需要获取下一数值
	     * 因此将这种情况强制应用成同一写数据源
	     */
	    DynamicDataSourceGlobal dynamicDataSourceGlobal = CACHE_MAP.get(ms.getId());
	    if(!Objects.isNull(dynamicDataSourceGlobal)) {
	        return dynamicDataSourceGlobal;
	    }
	    
	    /**
	     * 如果不是select的直接返回写数据源
	     */
	    if(!ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
	        return DynamicDataSourceGlobal.WRITE;
	    }
	    
	    BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
        
        /**
         * 如果在xml中用select标签写insert语句，则还是用写数据源
         */
        if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX) || sql.matches(REGEX)) {
            dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
            CACHE_MAP.put(ms.getId(), dynamicDataSourceGlobal);
        }else {
            dynamicDataSourceGlobal = DynamicDataSourceGlobal.READ;
        }
        
        return dynamicDataSourceGlobal;
	}

	/**
	 * 插件执行
	 */
	@Override
	public Object plugin(Object target) {
		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {

	}

}
